#include "bmp.h"
#include <cstring>
#include <iostream>

using namespace std;
using namespace riguz;

Bmp::Bmp(const char *fileName){	
	errno_t err = fopen_s(&this->_bmpFile, fileName, "rb");

	if (this->_bmpFile == NULL) {
		throw("File doesn't exits or wrong Filename.\n");
		return;
	}
	if (this->readHeader()) {
		this->_totalStuffablechar = (this->_fileHeader.bfSize - 54) / 8;
	}
}

Bmp::~Bmp() {
	if (this->_bmpFile)
		fclose(this->_bmpFile);
}

bool Bmp::readHeader() {
	fread((unsigned short int *)&this->_fileHeader.bfType, 2, 1, this->_bmpFile);
	// bitmap file uses little endian ,starts with: B 42 & M 4D
	if (this->_fileHeader.bfType != 0x4d42) { 
		throw("File is not a valid bitmap file.");
		return false;
	}
	// read file header
	fread((long *)&this->_fileHeader.bfSize, 4, 1, this->_bmpFile);
	fread((unsigned short int *)&this->_fileHeader.bfReserved1, 2, 1, this->_bmpFile);
	fread((unsigned short int *)&this->_fileHeader.bfReserved2, 2, 1, this->_bmpFile);
	fread((long *)&this->_fileHeader.bfOffBits, 4, 1, this->_bmpFile);
	
	// read info header
	fread((long *)&this->_infoHeader.biSize, 4, 1, this->_bmpFile);
	fread((long *)&this->_infoHeader.biWidth, 4, 1, this->_bmpFile);
	fread((long *)&this->_infoHeader.biHeight, 4, 1, this->_bmpFile);
	fread((unsigned short int *)&this->_infoHeader.biPlanes, 2, 1, this->_bmpFile);
	fread((unsigned short int *)&(this->_infoHeader.biBitCount), 2, 1, this->_bmpFile);
	fread((long *)&this->_infoHeader.biCompression, 4, 1, this->_bmpFile);
	fread((long *)&this->_infoHeader.biSizeImage, 4, 1, this->_bmpFile);
	fread((long *)&this->_infoHeader.biXPelsPerMeter, 4, 1, this->_bmpFile);
	fread((long *)&this->_infoHeader.biYPelsPerMeter, 4, 1, this->_bmpFile);
	fread((long *)&this->_infoHeader.biClrUsed, 4, 1, this->_bmpFile);
	fread((long *)&this->_infoHeader.biClrImportant, 4, 1, this->_bmpFile);
	
	return true;
}


int Bmp::encode(const char* secret, char* buffer, int bufferLen) {
	size_t dataLen = strlen(secret);
	if (dataLen > this->_totalStuffablechar) {
		throw("Data too long for this bitmap file.");
		return -1;
	}
	if (bufferLen < this->_infoHeader.biSize) {
		throw("Buffer size not enough.");
		return -1;
	}
	unsigned char header[BMP_HEADERS_SIZE];
	fseek(this->_bmpFile, 0, SEEK_SET);
	fread(header, BMP_HEADERS_SIZE, 1, this->_bmpFile);

	char* tmp = buffer; 
	memmove(tmp, header, BMP_HEADERS_SIZE * sizeof(char));	
	tmp += BMP_HEADERS_SIZE;

	char hideChar, bmpChar;
	long avalibleCharCount = this->_totalStuffablechar;
	long bmpFileSize = this->_fileHeader.bfSize - BMP_HEADERS_SIZE;

	cout << dataLen << endl;
	for (int i = 0; i < dataLen; i++) {
		hideChar = secret[i];
		for (int j = 0; j < 8; j++) {
			bmpChar = fgetc(this->_bmpFile);
			bmpChar &= 0xFE;                 //FE, to make sure LSB is always zero
			bmpChar |= (char)((hideChar >> j) & 1);

			// write a byte			
			*tmp++ = bmpChar;

			avalibleCharCount--;
			bmpFileSize--;
		}
	}
	// stuffing txt terminator indicator.
	char eoh = BMP_HIDE_EOF;
	for (int i = 0; i < 8; i++) {
		bmpChar = fgetc(this->_bmpFile);
		bmpChar &= 0xFE;
		bmpChar |= (char)((eoh >> i) & 1);		
		*tmp++ = bmpChar;

		bmpTotalStuffablechar--;
		bmpFileSize--;
	}

	// write remaing bmp bytes into the new bmp file.
	if (bmpFileSize != 0) {
		while (!feof(this->_bmpFile)) {
			bmpChar = fgetc(this->_bmpFile);
			*tmp++ = bmpChar;			
		}
	}
	return this->_fileHeader.bfSize;
}

int Bmp::decode(const char* bmp, size_t bmpLen, char *buffer, int bufferLen) {
	if (bmpLen <= BMP_HEADERS_SIZE) {
		throw("Bitmap buffer size not valid.");
		return -1;
	}
	int j = BMP_HEADERS_SIZE, d = 0;
	char byte[8];
	while (true) {
		if (bmpLen - j < 8)
			break;		
		for (int i = 0; i < 8; i++, j++) {
			
			byte[i] = bmp[j];
		}
		char ch = 0;
		for (int b = 7; b >= 0; b--) {
			ch += (byte[b] & 1);
			if (b != 0)
				ch <<= 1;
		}
		if (ch == EOF || ch == BMP_HIDE_EOF)
			break;
		if (d >= bufferLen) {
			throw("Decode buffer size not enough.");
			return -1;
		}
		buffer[d++] = ch;
	}
	buffer[d] = '\0';
	return d;
}